package br.com.iqt.advisor;

import br.com.iqt.AgentRewriter;
import br.com.iqt.AgentWorkLoad;
import br.com.iqt.Dbms;
import br.com.iqt.HeuristicsForSql;
import br.com.iqt.HeuristicsSelected;
import br.com.iqt.exception.DbmsException;
import br.com.iqt.exception.HeuristicsSelectedException;
import br.com.iqt.exception.NegativeNumberException;
import br.com.iqt.exception.SqlInputException;
import br.com.iqt.zql.ParseException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;

/**
 *
 * @author Arlino
 */
public class Advisor {

    private Dbms dbms;
    private boolean allSql = false, customSql = false;
    private String path = "";//path do diretório onde os arquivos com as recomendações de reescrita gerados.
    //Intevalo de tempo que o agente começar a executar as reescritas 
    // no modo on-the-fly (em milissegundos)
    private long timeInterval = 60 * 1000;
    /* Tipo de cash a ser utilizado: AgentRewriter.noCash (sem cash), 
     * AgentRewriter.sqlCash (cash de SQL) ou AgentRewriter.heuristicCash (cash de heurísticas).*/
    private int cashType;
    private ArrayList listHeuristicsCashed;

    public Advisor(Dbms dbms) {
        this.dbms = dbms;
        this.cashType = AgentRewriter.noCash;
        this.listHeuristicsCashed = null;
    }
    
    public Advisor(Dbms dbms, int cashType) throws FileNotFoundException, IOException {
        this.dbms = dbms;
        this.cashType = cashType;
        //recupera as heuristicas guardadas na cash
        if (cashType != AgentRewriter.noCash) {
            this.listHeuristicsCashed = AgentRewriter.getHeuristicsFromCash();
            if (this.listHeuristicsCashed == null) {
                this.listHeuristicsCashed = new ArrayList();
            }
        }
    }

    /*
     * public Advisor(Dbms dbms, boolean allSql, boolean customSql) {
        this.dbms = dbms;
        this.allSql = allSql;
        this.customSql = customSql;
    }
    */

    public void setPath(String path) {
        if (path != null && !path.equals("")) {
            path = path.trim();
            if (path.charAt(path.length() - 1) != '/') {
                path += "/";
            }
            this.path = path;
        }
    }

    public void start(long timeInterval) throws InterruptedException, DbmsException, ClassNotFoundException, SQLException, ParseException, SqlInputException, IOException, NegativeNumberException, java.text.ParseException, FileNotFoundException, HeuristicsSelectedException {
        if (timeInterval <= 0) {
            throw new NegativeNumberException();
        }
        this.timeInterval = timeInterval;
        this.start();
    }

    public void start() throws DbmsException, InterruptedException, ClassNotFoundException, SQLException, FileNotFoundException, IOException, HeuristicsSelectedException, SqlInputException {
        if (dbms == null) {
            throw new DbmsException();
        }
        //arquivo com carga de trabalho (coletada periodicamente)
        File fileWorkload = new File("WorkloadFile.txt");
        //arquivo de relatorio com as recomendações de reescrita
        File fileReport = new File("RewritesReport.txt");

        AgentWorkLoad awl = new AgentWorkLoad(this.dbms);
        AgentRewriter ar = new AgentRewriter(this.dbms);
        int count = 0;
        while (true) {
            Thread.sleep(this.timeInterval);
            //coleta carga de trabalho
            ArrayList newWorkLoad = awl.getWorkLoad();
            //System.out.println("carga de trabalho coletada");
            if (newWorkLoad.size() > 0) {
                count++;
                
                //recupera a carga de trabalho obtida anteriormente no arquivo WorkloadFile.txt
                ArrayList oldWorkload = new ArrayList();
                FileReader fileReader = new FileReader(fileWorkload);
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    oldWorkload.add(line);
                }
                fileReader.close();
                bufferedReader.close();

                //verifica se cada sql da nova carga de trabalho é diferente das sql ja armazenadas anteiormente
                
                ArrayList sqlsToRemove = new ArrayList();//vetor de sqls a serem removidas
                for (Object oldSql : oldWorkload) {
                    for (Object newSql : newWorkLoad) {
                        //se a sql antiga for igual a alguma nova sql, entao adiciona ao veto de sqls a serem removidas
                        if (oldSql.toString().equalsIgnoreCase(newSql.toString())) {
                            sqlsToRemove.add(newSql);
                        }
                    }
                }
                
                //deleta as sqls a do vetor de sqls a serem removidas (sqlsToRemove) no vetor de sqls novas (newWorkLoad)
                for (Object sql : sqlsToRemove) {
                    newWorkLoad.remove(sql);
                }

                //armazena as novas sqls capturadas no arquivo WorkloadFile.txt
                if (newWorkLoad.size() > 0) {
                    fileWorkload.createNewFile();
                    FileWriter fileWriter = new FileWriter(fileWorkload, true);
                    PrintWriter printWriter = new PrintWriter(fileWriter);
                    for (Object wf : newWorkLoad) {
                        printWriter.println(wf.toString());
                    }
                    printWriter.flush();
                    printWriter.close();
                }
            }

            //a cada 4 ciclos de coletas de carga de trabalho é feita uma analise de reescrita
            if (count >= 4) {
                count = 0;
                
                //copia as sql do arquivo de workload para um vetor
                ArrayList workloadFile = new ArrayList();
                FileReader fileReader = new FileReader(fileWorkload);
                BufferedReader bufferedReader = new BufferedReader(fileReader);
                String line;
                while ((line = bufferedReader.readLine()) != null) {
                    workloadFile.add(line);
                }
                fileReader.close();
                bufferedReader.close();
                
                //pocura por reescritas e se encontrar guarda as sugetões de reescrita em um arquivo
                FileWriter fileWriter = new FileWriter(fileReport, false);
                PrintWriter printWriter = new PrintWriter(fileWriter);
                for (Object sql : workloadFile) {
                    HeuristicsSelected heuristicsSelected = null;
                    boolean cashUsed = false;//informa se houve uso da cash
                    String rewritedSql = null;
                    boolean isRewrited = false;
                    try {
                        switch (this.cashType) {
                            //Se nao for utiliza cash, tenta reescrever utilizando todas as heuristicas disponiveis
                            case AgentRewriter.noCash:
                                rewritedSql = ar.analyseStatement(sql.toString());
                                cashUsed = false;
                                isRewrited = ar.isRewrited();
                                break;
                            //Se utilizar a cash de sqls reescritas, tenta obter uma sql na cash. Se nao consguir, 
                            //tenta reescrever utilizando todas as heuristicas disponiveis
                            case AgentRewriter.sqlCash:
                                rewritedSql = AgentRewriter.getSqlRewritedCashed(sql.toString(), listHeuristicsCashed);
                                if (rewritedSql != null) {
                                    cashUsed = true;
                                    isRewrited = true;
                                } else {
                                    cashUsed = false;
                                    rewritedSql = ar.analyseStatement(sql.toString());
                                    isRewrited = ar.isRewrited();
                                }
                                break;
                            //Se utilizar a cash de heuristicas, tenta obter as heuristicas na cash. Se nao consguir, 
                            //tenta reescrever utilizando todas as heuristicas disponiveis
                            case AgentRewriter.heuristicCash:
                                heuristicsSelected = AgentRewriter.getHeuristicCashed(sql.toString(), listHeuristicsCashed);
                                if (heuristicsSelected != null) {
                                    cashUsed = true;
                                } else {
                                    cashUsed = false;
                                    heuristicsSelected = null;
                                }
                                rewritedSql = ar.analyseStatement(sql.toString(), heuristicsSelected);
                                isRewrited = ar.isRewrited();
                                break;
                        }

                        //se tiver conseguido reescrever uma consulta que não está na cash, entao a armazena na cash
                        if ((this.cashType != AgentRewriter.noCash) && (!cashUsed) && (isRewrited)) {
                            
                            heuristicsSelected = ar.getHeuristicsSelected();
                            AgentRewriter.addHeuristicCashed(sql.toString(), rewritedSql, heuristicsSelected);
                            
                            //adiciona a nova heuristica ao array lista sqls vindas do cash
                            HeuristicsForSql HeuristicsForSql = new HeuristicsForSql(sql.toString(), rewritedSql, heuristicsSelected);
                            listHeuristicsCashed.add(HeuristicsForSql);
                        }
                    } catch (ParseException ex) {
                        //Logger.getLogger(Advisor.class.getName()).log(Level.SEVERE, null, ex);
                        //System.out.println("Erro: " + ex.getMessage());
                    } catch (SqlInputException ex) {
                        //Logger.getLogger(Advisor.class.getName()).log(Level.SEVERE, null, ex);
                        System.out.println("Erro: " + ex.getMessage());
                    } catch (java.text.ParseException ex) {
                        //Logger.getLogger(Advisor.class.getName()).log(Level.SEVERE, null, ex);
                        System.out.println("Erro: " + ex.getMessage());
                    }
                    
                    if (isRewrited) {
                        printWriter.println("Orginal SQL: " + sql.toString());
                        printWriter.println("Rewrited SQL: " + rewritedSql);
                        printWriter.println();
                    }
                }
                printWriter.flush();
                printWriter.close();
            }
        }
    }
}
